package com.crunch.math;

/**
 * A class for lines in 2D, represented by the equation ax + by = c. The vector (a,b) is considered the line's normal.
 */
public final class Linef {
	Vector2f normal;
	float c;

	/**
	 * Constructs a line from the equation ax + by = c.
	 */
	public Linef(Vector2f normal, float c) {
		this.normal = new Vector2f(normal);
		this.c = c;
	}

	/**
	 * Sets the line from the equation ax + by = c.
	 */
	public void set(Vector2f normal, float c) {
		this.normal.set(normal);
		this.c = c;
	}

	/**
	 * Constructs a line by copying the one provided.
	 */
	public Linef(Linef l) {
		this.normal = new Vector2f(l.normal);
		this.c = l.c;
	}

	/**
	 * Sets the line by copying the one provided.
	 */
	public void set(Linef l) {
		normal.set(l.normal);
		c = l.c;
	}

	/**
	 * Constructs a line from two points.
	 */
	public static Linef line2Points(Vector2f a, Vector2f b) {
		Vector2f normal = b.subtract(a);
		normal.rotateCcwAssign();
		float c = MathOps.dot(a, normal);
		return new Linef(normal, c);
	}

	/**
	 * Sets the line from two points.
	 */
	public void set2Points(Vector2f a, Vector2f b) {
		normal.set(b.subtract(a));
		normal.rotateCcwAssign();
		c = MathOps.dot(a, normal);
	}

	/**
	 * Constructs a line from a point and a normal.
	 */
	public static Linef linePointNormal(Vector2f point, Vector2f normal) {
		// ax + by = c
		float c = MathOps.dot(point, normal);
		return new Linef(normal, c);
	}

	/**
	 * Sets the line from a point and a normal.
	 */
	public void setPointNormal(Vector2f point, Vector2f normal) {
		this.normal.set(normal);
		c = MathOps.dot(point, normal);
	}

	/**
	 * Returns the normal.
	 */
	public Vector2f getNormal() {
		return new Vector2f(normal);
	}

	/**
	 * Sets the normal.
	 */
	public void setNormal(Vector2f normal) {
		this.normal.set(normal);
	}

	/**
	 * Returns c.
	 */
	public float getC() {
		return c;
	}

	/**
	 * Sets c.
	 */
	public void setC(float c) {
		this.c = c;
	}

	/**
	 * Returns whether this line is equal to the object {@code o}.
	 */
	@Override
	public boolean equals(Object o) {
		if (o == this) {
			return true;
		} else if (o == null || !(o instanceof Linef)) {
			return false;
		} else {
			return equals((Linef) o);
		}
	}

	/**
	 * Returns a hash code for this object.
	 */
	@Override
	public int hashCode() {
		return normal.hashCode() ^ Float.floatToIntBits(c);
	}

	/**
	 * Returns whether this line is equal to the line {@code l}.
	 */
	public boolean equals(Linef l) {
		return normal.equals(l.normal) && c == l.c;
	}

	/**
	 * Returns a normalized version of this line.
	 */
	public Linef normalize() {
		return new Linef(this).normalizeAssign();
	}

	/**
	 * Normalizes this line.
	 */
	public Linef normalizeAssign() {
		float invMag = 1.0f / normal.magnitude();
		normal.multiply(invMag);
		c *= invMag;
		return this;
	}

	/**
	 * Computes the intersection between the line and a ray. The intersection time is returned (in terms of the ray's
	 * origin and direction), or {@link Float#POSITIVE_INFINITY} if no intersection occurred.
	 */
	public float intersect(Ray2f ray) {
		// p = o + td
		// want: n.p = c
		// n.(o + td) = c
		// n.o + t*n.d = c
		// t = (c - n.o) / n.d
		float nDotD = MathOps.dot(normal, ray.getDirection());
		if (nDotD == 0.0f) {
			return Float.POSITIVE_INFINITY;
		} else {
			return (c - MathOps.dot(normal, ray.getOrigin())) / nDotD;
		}
	}

	/**
	 * Computes the distance between a point and the line. The distance is scaled with the magnitude of the normal.
	 */
	public float distance(Vector2f point) {
		return Math.abs(signedDistance(point));
	}

	/**
	 * Computes the signed distance between a point and the line. The distance is scaled with the magnitude of the
	 * normal.
	 */
	public float signedDistance(Vector2f point) {
		return MathOps.dot(normal, point) - c;
	}

	/**
	 * Returns which side of the line a point is on: -1, 0, or 1 for behind, on, or in front of the line, respectively.
	 */
	public float side(Vector2f point) {
		return Math.signum(signedDistance(point));
	}
}